﻿#region License

// This file is part of Grawlix.
// 
// Grawlix is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// Grawlix is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public
// License along with Grawlix.  If not, see
// <http://www.gnu.org/licenses/>.

#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;

namespace Grawlix.Geometry
{
	/// <summary>
	/// An immutable pair of Cartesian coordinates, used to
	/// represent displacement, position or dimension.
	/// </summary>
	[Serializable]
	public struct Vector2 : IEquatable<Vector2>, IFormattable
	{
		/// <summary>
		/// The Zero vector.
		/// </summary>
		public static readonly Vector2 Zero = new Vector2(0, 0);

		/// <summary>
		/// The x-coordinate of the Cartesian pair.
		/// </summary>
		private readonly float _x;

		/// <summary>
		/// The y-coordinate of the Cartesian pair.
		/// </summary>
		private readonly float _y;

		/// <summary>
		/// Initializes a <see cref="Vector2"/> structure from
		/// Cartesian coordinates.
		/// </summary>
		/// <param name="x">The x-coordinate.</param>
		/// <param name="y">The y-coordinate.</param>
		[SuppressMessage("Microsoft.Naming", "CA1704",
			Justification =
				"X and Y make sense for Cartesian coordinates.")]
		public Vector2(float x, float y)
		{
			_x = x;
			_y = y;
		}

		/// <summary>
		/// The angle, in radians of this <see cref="Vector2"/>
		/// structure. 0 radians points in the positive x direction. pi
		/// / 2 radians points in the positive y direction.
		/// </summary>
		public float Angle
		{
			get { return (float)Math.Atan2(Y, X); }
		}

		/// <summary>
		/// The positive or 0 length of this <see cref="Vector2"/> Structure.
		/// </summary>
		public float Magnitude
		{
			get { return (float)Math.Sqrt(InnerMultiply(this)); }
		}

		/// <summary>
		/// The <see cref="Rotation2"/> of this <see cref="Vector2"/>
		/// structure. 0 radians points in the positive x direction. pi
		/// / 2 radians points in the positive y direction.
		/// </summary>
		public Rotation2 Rotation
		{
			get
			{
				Vector2 norm = Normalize();
				return Rotation2.FromCosSin(norm.X, norm.Y);
			}
		}

		/// <summary>
		/// The x-coordinate of the Cartesian pair.
		/// </summary>
		[SuppressMessage("Microsoft.Naming", "CA1704",
			Justification =
				"X and Y make sense for Cartesian coordinates.")]
		public float X
		{
			get { return _x; }
		}

		/// <summary>
		/// The y-coordinate of the Cartesian pair.
		/// </summary>
		[SuppressMessage("Microsoft.Naming", "CA1704",
			Justification =
				"X and Y make sense for Cartesian coordinates.")]
		public float Y
		{
			get { return _y; }
		}

		/// <summary>
		/// Computes the sum of two <see cref="Vector2"/> structures.
		/// </summary>
		/// <param name="rhs">The right-hand addend.</param>
		/// <returns>The sum of the two <see cref="Vector2"/>
		/// structures.</returns>
		public Vector2 Add(Vector2 rhs)
		{
			return this + rhs;
		}

		/// <summary>
		/// Computes the array (member-wise) quotient of two 
		/// <see cref="Vector2"/> structures.
		/// </summary>
		/// <param name="rhs">The divisor.</param>
		/// <returns>
		/// The array quotient of the two <see cref="Vector2"/>
		/// structures.</returns>
		public Vector2 ArrayDivide(Vector2 rhs)
		{
			return new Vector2(X / rhs.X, Y / rhs.Y);
		}

		/// <summary>
		/// Computes the array (member-wise) product of two 
		/// <see cref="Vector2"/> structures.
		/// </summary>
		/// <param name="rhs">The multiplier.</param>
		/// <returns>
		/// The array product of the two <see cref="Vector2"/>
		/// structures.</returns>
		public Vector2 ArrayMultiply(Vector2 rhs)
		{
			return new Vector2(X * rhs.X, Y * rhs.Y);
		}

		/// <summary>
		/// Computes the product of a <see cref="Vector2"/> structure
		/// an inverted scalar.
		/// </summary>
		/// <param name="scalar">
		/// The reciprocal of the scalar to multiply.</param>
		/// <returns>The result of the vector scaled by the scalar.
		/// </returns>
		public Vector2 Divide(float scalar)
		{
			return this / scalar;
		}

		/// <summary>
		/// Indicates whether this instance and a specified object are
		/// equal.
		/// </summary>
		/// <returns>
		/// true if <paramref name="obj" /> and this instance are the
		/// same type and represent the same value; otherwise, false.
		/// </returns>
		/// <param name="obj">Another object to compare to.</param>
		public override bool Equals(object obj)
		{
			return obj.GetType() == typeof(Vector2) &&
			       Equals((Vector2)obj);
		}

		/// <summary>
		/// Indicates whether the current object is equal to another
		/// object of the same type.
		/// </summary>
		/// <returns>
		/// true if the current object is equal to the
		/// <paramref name="other" /> parameter; otherwise, false.
		/// </returns>
		/// <param name="other">An object to compare with this object.
		/// </param>
		public bool Equals(Vector2 other)
		{
			return other._x == _x && other._y == _y;
		}

		/// <summary>
		/// Computes the magnitude of the exterior (wedge) product of
		/// two <see cref="Vector2"/> structures.
		/// </summary>
		/// <param name="rhs">The multiplier.</param>
		/// <returns>
		/// The magnitude of the inner product of the two 
		/// <see cref="Vector2"/> structures.</returns>
		/// <remarks>
		/// <para>
		/// This product comes out to the signed area of a
		/// parallelogram specified by the vectors, or their magnitudes
		/// multiplied by the sine of the angle between them. It is
		/// also the determinant of a 2x2 matrix with these vectors
		/// specified as column vectors.
		/// </para>
		/// <para>
		/// The result of the wedge product in two dimensions is not a
		/// scalar, but a 2-multivector. The magnitude of that
		/// 2-multivector is returned.
		/// </para>
		/// <para>
		/// The exterior product is a different concept than the outer
		/// (tensor) product.
		/// </para>
		/// </remarks>
		/// <seealso cref="TensorMultiply"/>
		/// <seealso cref="InnerMultiply"/>
		public float ExteriorMultiply(Vector2 rhs)
		{
			return X * rhs.Y - Y * rhs.X;
		}

		/// <summary>
		/// Initialize a <see cref="Vector2"/> structure from polar
		/// coordinates.
		/// </summary>
		/// <param name="magnitude">
		/// The (positive) length of the <see cref="Vector2"/>.
		/// </param>
		/// <param name="angle">
		/// The angle of the <see cref="Vector2"/>, in radians. 0
		/// radians points in the positive x direction. pi / 2 radians
		/// points in the positive y direction.
		/// </param>
		/// <returns>A new <see cref="Vector2"/> structure with the
		/// given <paramref name="magnitude"/> and
		/// <paramref name="angle"/>.</returns>
		/// <exception cref="ArgumentException">
		/// magnitude is not greater than or equal to 0.</exception>
		public static Vector2 FromPolarCoordinates(
			float magnitude, float angle)
		{
			// Accounting for NaN
			if (!(magnitude >= 0))
			{
				throw new ArgumentException(
					"Expected: (magnitude >= 0)", "magnitude");
			}
			return new Vector2(magnitude * (float)Math.Cos(angle),
			                   magnitude * (float)Math.Sin(angle));
		}

		/// <summary>
		/// Initialize a <see cref="Vector2"/> structure from polar
		/// coordinates.
		/// </summary>
		/// <param name="magnitude">
		/// The (positive) length of the <see cref="Vector2"/>.
		/// </param>
		/// <param name="rotation">
		/// The <see cref="Rotation2"/> of the <see cref="Vector2"/>. 0
		/// radians points in the positive x direction. pi / 2 radians
		/// points in the positive y direction.
		/// </param>
		/// <returns>A new <see cref="Vector2"/> structure with the
		/// given <paramref name="magnitude"/> and
		/// <paramref name="rotation"/>.</returns>
		/// <exception cref="ArgumentException">
		/// magnitude is not greater than or equal to 0.</exception>
		public static Vector2 FromPolarCoordinates(
			float magnitude, Rotation2 rotation)
		{
			// Accounting for NaN
			if (!(magnitude >= 0))
			{
				throw new ArgumentException(
					"Expected: (magnitude >= 0)", "magnitude");
			}
			return new Vector2(magnitude * rotation.Cos,
			                   magnitude * rotation.Sin);
		}

		/// <summary>
		/// Returns the hash code for this instance.
		/// </summary>
		/// <returns>
		/// A 32-bit signed integer that is the hash code for this
		/// instance.
		/// </returns>
		public override int GetHashCode()
		{
			unchecked
			{
				return (_x.GetHashCode() * 397) ^ _y.GetHashCode();
			}
		}

		/// <summary>
		/// Computes the inner (dot) product of two 
		/// <see cref="Vector2"/> structures.
		/// </summary>
		/// <param name="rhs">The multiplier.</param>
		/// <returns>
		/// The inner product of the two <see cref="Vector2"/>
		/// structures.</returns>
		/// <remarks>
		/// This result comes out to the magnitudes of the two vectors
		/// times the cosine of the angle between them.
		/// </remarks>
		/// <seealso cref="TensorMultiply"/>
		/// <seealso cref="ExteriorMultiply"/>
		public float InnerMultiply(Vector2 rhs)
		{
			return X * rhs.X + Y * rhs.Y;
		}

		/// <summary>
		/// Computes the product of a <see cref="Vector2"/> structure a
		/// scalar.
		/// </summary>
		/// <param name="scalar">The scalar to multiply.</param>
		/// <returns>The result of the vector scaled by the scalar.
		/// </returns>
		public Vector2 Multiply(float scalar)
		{
			return this * scalar;
		}

		/// <summary>
		/// Computes the negation of a <see cref="Vector2"/> structure.
		/// </summary>
		/// <returns>The negated <see cref="Vector2"/> structure.
		/// </returns>
		public Vector2 Negate()
		{
			return -this;
		}

		/// <summary>
		/// Creates a normalized <see cref="Vector2"/> facing the same
		/// direction as the given <see cref="Vector2"/>.
		/// </summary>
		/// <returns>
		/// A <see cref="Vector2"/> with length 1 and the same
		/// direction as this. If the current <see cref="Vector2"/> has
		/// no direction, it returns one pointing in the positive x
		/// direction.
		/// </returns>
		public Vector2 Normalize()
		{
			float magnitude = Magnitude;
			if (magnitude > 0)
				return this / magnitude;
			return new Vector2(1, 0);
		}

		/// <summary>
		/// Computes the difference between two <see cref="Vector2"/>
		/// structures.
		/// </summary>
		/// <param name="rhs">The subtrahend.</param>
		/// <returns>The difference of the two <see cref="Vector2"/>
		/// structures.</returns>
		public Vector2 Subtract(Vector2 rhs)
		{
			return this - rhs;
		}

		/// <summary>
		/// Computes the tensor (outer) product of two 
		/// <see cref="Vector2"/> structures.
		/// </summary>
		/// <param name="rhs">The multiplier.</param>
		/// <returns>
		/// The tensor product of the two <see cref="Vector2"/>
		/// structures.</returns>
		/// <remarks>
		/// <para>
		/// The result is a matrix with the four values: (x1*x2, y1*x2,
		/// x1*y2, y1*y2), specified in column-major order.
		/// </para>
		/// <para>
		/// The tensor product is a different concept than the exterior
		/// product.
		/// </para>
		/// </remarks>
		/// <seealso cref="ExteriorMultiply"/>
		/// <seealso cref="InnerMultiply"/>
		public Matrix22 TensorMultiply(Vector2 rhs)
		{
			return new Matrix22(X * rhs.X, Y * rhs.X, X * rhs.Y,
			                    Y * rhs.Y);
		}

		/// <summary>
		/// Formats the value of the current instance using numbers in
		/// the specified format.
		/// </summary>
		/// <returns>
		/// A <see cref="T:System.String" /> containing the value of
		/// the current instance in the specified format.
		/// </returns>
		/// <param name="format">The <see cref="T:System.String" />
		/// specifying the number format to use, or null to specify the
		/// default.
		/// </param>
		/// <param name="formatProvider">The
		/// <see cref="T:System.IFormatProvider" /> to use to format
		/// the value, or null to obtain the numeric format information
		/// from the current locale setting of the operating system. 
		/// </param>
		public string ToString(string format,
		                       IFormatProvider formatProvider)
		{
			return "(" + X.ToString(format, formatProvider) + ", " +
			       Y.ToString(format, formatProvider) + ")";
		}

		/// <summary>
		/// Obtains a string representation of this object.
		/// </summary>
		/// <returns>A string representation of this object.</returns>
		public override string ToString()
		{
			return ToString(null, null);
		}

		/// <summary>
		/// Computes the sum of two <see cref="Vector2"/> structures.
		/// </summary>
		/// <param name="lhs">The left-hand addend.</param>
		/// <param name="rhs">The right-hand addend.</param>
		/// <returns>The sum of the two <see cref="Vector2"/>
		/// structures.</returns>
		public static Vector2 operator +(Vector2 lhs, Vector2 rhs)
		{
			return new Vector2(lhs.X + rhs.X, lhs.Y + rhs.Y);
		}

		/// <summary>
		/// Computes the product of a <see cref="Vector2"/> structure
		/// an inverted scalar.
		/// </summary>
		/// <param name="vector">The vector to multiply.</param>
		/// <param name="scalar">
		/// The reciprocal of the scalar to multiply.</param>
		/// <returns>The result of the vector scaled by the scalar.
		/// </returns>
		public static Vector2 operator /(
			Vector2 vector, float scalar)
		{
			return new Vector2(vector.X / scalar, vector.Y / scalar);
		}

		/// <summary>
		/// Computes the product of the member-wise inversion of a 
		/// <see cref="Vector2"/> structure a scalar.
		/// </summary>
		/// <param name="vector">The member-wise reciprocal of the
		/// vector to multiply.</param>
		/// <param name="scalar">The scalar to multiply.</param>
		/// <returns>The result of the vector scaled by the scalar.
		/// </returns>
		public static Vector2 operator /(
			float scalar, Vector2 vector)
		{
			return new Vector2(scalar / vector.X, scalar / vector.Y);
		}

		/// <summary>
		/// Determines whether two <see cref="Vector2"/> objects
		/// have the same value.
		/// </summary>
		/// <param name="left">A <see cref="Vector2"/>.</param>
		/// <param name="right">A <see cref="Vector2"/>.</param>
		/// <returns>true if the value of <paramref name="left"/> is the
		/// same as <paramref name="right"/>; else false.</returns>
		public static bool operator ==(Vector2 left, Vector2 right
			)
		{
			return left.Equals(right);
		}

		/// <summary>
		/// Determines whether two <see cref="Vector2"/> objects have
		/// different values.
		/// </summary>
		/// <param name="left">A <see cref="Vector2"/>.</param>
		/// <param name="right">A <see cref="Vector2"/>.</param>
		/// <returns>true if the value of <paramref name="left"/> is
		/// different from <paramref name="right"/>; else false.
		/// </returns>
		public static bool operator !=(Vector2 left, Vector2 right
			)
		{
			return !left.Equals(right);
		}

		/// <summary>
		/// Computes the product of a <see cref="Vector2"/> structure
		/// and a scalar.
		/// </summary>
		/// <param name="vector">The vector to multiply.</param>
		/// <param name="scalar">The scalar to multiply.</param>
		/// <returns>The result of the vector scaled by the scalar.
		/// </returns>
		public static Vector2 operator *(
			Vector2 vector, float scalar)
		{
			return new Vector2(vector.X * scalar, vector.Y * scalar);
		}

		/// <summary>
		/// Computes the product of a <see cref="Vector2"/> structure
		/// and a scalar.
		/// </summary>
		/// <param name="vector">The vector to multiply.</param>
		/// <param name="scalar">The scalar to multiply.</param>
		/// <returns>The result of the vector scaled by the scalar.
		/// </returns>
		public static Vector2 operator *(
			float scalar, Vector2 vector)
		{
			return new Vector2(vector.X * scalar, vector.Y * scalar);
		}

		/// <summary>
		/// Computes the difference between two <see cref="Vector2"/>
		/// structures.
		/// </summary>
		/// <param name="lhs">The minuend.</param>
		/// <param name="rhs">The subtrahend.</param>
		/// <returns>The difference of the two <see cref="Vector2"/>
		/// structures.</returns>
		public static Vector2 operator -(Vector2 lhs, Vector2 rhs)
		{
			return new Vector2(lhs.X - rhs.X, lhs.Y - rhs.Y);
		}

		/// <summary>
		/// Computes the negation of a <see cref="Vector2"/> structure.
		/// </summary>
		/// <param name="vector">The vector to negate.</param>
		/// <returns>The negated <see cref="Vector2"/> structure.
		/// </returns>
		public static Vector2 operator -(Vector2 vector)
		{
			return new Vector2(-vector.X, -vector.Y);
		}
	}

	/// <summary>
	/// Compares two Vector2 objects lexicographically: first order
	/// by X, then Y.
	/// </summary>
	public class LexicographicVector2Comparer :
		IComparer<Vector2>
	{
		/// <summary>
		///Compares two objects and returns a value indicating whether
		///one is less than, equal to, or greater than the other.
		/// </summary>
		/// <returns>
		///Value Condition Less than zero<paramref name="x" /> is less
		///than <paramref name="y" />.Zero<paramref name="x" />equals 
		///<paramref name="y" />.Greater than zero<paramref name="x" />
		///is greater than <paramref name="y" />.
		/// </returns>
		/// <param name="x">The first object to compare.</param>
		/// <param name="y">The second object to compare.</param>
		public int Compare(Vector2 x, Vector2 y)
		{
			int value = x.X.CompareTo(y.X);
			if (value == 0)
				value = x.Y.CompareTo(y.Y);
			return value;
		}
	}
}